﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows;
using System.Windows.Threading;
using System.Windows.Shapes;
using System.Windows.Media;
using System.ComponentModel;

namespace GraphosControls
{
	public enum VisualEdgeType
	{
		Primary,
		Secondary,
		Transition
	}
	public enum ArrowType
	{
		None,
		Single,
		Double
	}
	public class VisualEdge : Shape, INotifyPropertyChanged
	{
		public VisualEdge(VisualNode start, VisualNode end)
		{
			DataContext = this;

			m_startNode = start;
			m_endNode = end;

			// set up bindings for this edge
			Binding startBinding = new Binding("Position");
			startBinding.Source = m_startNode;
			Binding endBinding = new Binding("Position");
			endBinding.Source = m_endNode;

			SetBinding(VisualEdge.StartPointProperty, startBinding);
			SetBinding(VisualEdge.EndPointProperty, endBinding);

            //CreateNewLabel();
		}

        void m_tbLabel_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            Vector v = m_startNode.Position - m_endNode.Position;
            double diff1 = v.Length / (Math.Max(m_startNode.Center.X, m_startNode.Center.Y) + 2);
            double diff2 = v.Length / (Math.Max(m_endNode.Center.X, m_endNode.Center.Y) + 2);

            Point pt1 = new Point(StartPoint.X - v.X / diff1, StartPoint.Y - v.Y / diff1);
            Point pt2 = new Point(EndPoint.X + v.X / diff2, EndPoint.Y + v.Y / diff2);
            RepositionLabel(pt1, pt2);
        }



		#region Dependency Properties

		public Point StartPoint
		{
			get { return (Point)GetValue(StartPointProperty); }
			set { SetValue(StartPointProperty, value); }
		}
		public static readonly DependencyProperty StartPointProperty =
			 DependencyProperty.Register("StartPoint", typeof(Point), typeof(VisualEdge), 
             new FrameworkPropertyMetadata(new Point(), FrameworkPropertyMetadataOptions.AffectsMeasure));

		public Point EndPoint
		{
			get { return (Point)GetValue(EndPointProperty); }
			set { SetValue(EndPointProperty, value); }
		}
		public static readonly DependencyProperty EndPointProperty =
				 DependencyProperty.Register("EndPoint", typeof(Point), typeof(VisualEdge), 
                 new FrameworkPropertyMetadata(new Point(), FrameworkPropertyMetadataOptions.AffectsMeasure));

        public Point Center
        {
            get { return (Point)GetValue(CenterProperty); }
            set { SetValue(CenterProperty, value); }
        }
        public static readonly DependencyProperty CenterProperty =
            DependencyProperty.Register("Center", typeof(Point), typeof(VisualEdge), 
            new FrameworkPropertyMetadata(new Point(), FrameworkPropertyMetadataOptions.None));





        public VisualNode CenterNode
        {
            get { return (VisualNode)GetValue(CenterNodeProperty); }
            set { SetValue(CenterNodeProperty, value); }
        }

        // Using a DependencyProperty as the backing store for CenterNode.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty CenterNodeProperty =
            DependencyProperty.Register("CenterNode", typeof(VisualNode), typeof(VisualEdge), 
            new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsMeasure));




        public VisualNode ControlNodeOne
        {
            get { return (VisualNode)GetValue(ControlNodeOneProperty); }
            set { SetValue(ControlNodeOneProperty, value); }
        }

        // Using a DependencyProperty as the backing store for ControlNodeOne.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ControlNodeOneProperty =
            DependencyProperty.Register("ControlNodeOne", typeof(VisualNode), typeof(VisualEdge),
            new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsMeasure));




        public VisualNode ControlNodeTwo
        {
            get { return (VisualNode)GetValue(ControlNodeTwoProperty); }
            set { SetValue(ControlNodeTwoProperty, value); }
        }

        // Using a DependencyProperty as the backing store for ControlNodeTwo.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ControlNodeTwoProperty =
            DependencyProperty.Register("ControlNodeTwo", typeof(VisualNode), typeof(VisualEdge),
            new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsMeasure));



		public VisualEdgeType Type
		{
			get { return (VisualEdgeType)GetValue(TypeProperty); }
			set { SetValue(TypeProperty, value); }
		}
		public static readonly DependencyProperty TypeProperty =
			 DependencyProperty.Register("Type", typeof(VisualEdgeType), typeof(VisualEdge), new UIPropertyMetadata(VisualEdgeType.Primary));

		public ArrowType Arrow
		{
			get { return (ArrowType)GetValue(ArrowProperty); }
			set { SetValue(ArrowProperty, value); }
		}
		public static readonly DependencyProperty ArrowProperty =
			 DependencyProperty.Register("Arrow", typeof(ArrowType), typeof(VisualEdge), new UIPropertyMetadata(ArrowType.None));

		public double HeadWidth
		{
			get { return (double)GetValue(HeadWidthProperty); }
			set { SetValue(HeadWidthProperty, value); }
		}
		public static readonly DependencyProperty HeadWidthProperty =
			 DependencyProperty.Register("HeadWidth", typeof(double), typeof(VisualEdge), new UIPropertyMetadata(0.0));

		public double HeadHeight
		{
			get { return (double)GetValue(HeadHeightProperty); }
			set { SetValue(HeadHeightProperty, value); }
		}
		public static readonly DependencyProperty HeadHeightProperty =
			 DependencyProperty.Register("HeadHeight", typeof(double), typeof(VisualEdge), new UIPropertyMetadata(0.0));

		#endregion

		#region Properties

		public VisualNode VisualNodeFrom
		{
			get { return m_startNode; }
		}
		public VisualNode VisualNodeTo
		{
			get { return m_endNode; }
		}

       public double Length
       {
           get { return (m_startNode.Position - m_endNode.Position).Length; }
       }

        public Brush VisualEdgeStrokeBrush
        {
            get 
            {
                string str = m_endNode.Node.Person.Gender.ToString() + Type.ToString() + "EdgeBrush";
                Brush brush = (Brush)TryFindResource(str);
                if (brush == null)
                    return Brushes.White;
                return brush;
            }
        }
       

        public TextBlock LabelTextBlock
        {
            get { return m_tbLabel; }
        }
		#endregion

		#region Overrides

		protected override void OnInitialized(EventArgs e)
		{
			base.OnInitialized(e);
            Style = (Style)TryFindResource("VisualEdgeStyle");
            CreateNewLabel();
            UpdateStyles();
        }

		#endregion

        // swap the start point and the end point for this edge
		public void SwitchDirection()
		{
			VisualNode save = m_startNode;
			m_startNode = m_endNode;
			m_endNode = save;

			BindingOperations.ClearAllBindings(this);

			Binding startBinding = new Binding("Position");
			startBinding.Source = m_startNode;
            Binding endBinding = new Binding("Position");
			endBinding.Source = m_endNode;

			SetBinding(VisualEdge.StartPointProperty, startBinding);
			SetBinding(VisualEdge.EndPointProperty, endBinding);

            CreateNewLabel();
            UpdateStyles();
		}

		private VisualNode m_startNode;
		private VisualNode m_endNode;

		protected override Geometry DefiningGeometry
		{
			get
			{
				// Create a StreamGeometry for describing the shape
				StreamGeometry geometry = new StreamGeometry();
				geometry.FillRule = FillRule.EvenOdd;

				using (StreamGeometryContext context = geometry.Open())
				{
					InternalDrawArrowGeometry(context);
				}

				// Freeze the geometry for performance benefits
				geometry.Freeze();

				return geometry;
			}
		}
		private void InternalDrawArrowGeometry(StreamGeometryContext context)
		{
           
			//VisualEdge visualEdge = sender as VisualEdge;
			Vector v = m_startNode.Position - m_endNode.Position;
			double diff1 = v.Length / (Math.Max(m_startNode.Center.X, m_startNode.Center.Y) + 2);
			double diff2 = v.Length / (Math.Max(m_endNode.Center.X, m_endNode.Center.Y) + 2);
			
            Point pt1 = new Point(StartPoint.X - v.X / diff1, StartPoint.Y - v.Y / diff1);
			Point pt2 = new Point(EndPoint.X + v.X / diff2, EndPoint.Y + v.Y / diff2);

            Point midPoint = RepositionLabel(pt1, pt2);

            Point arrowPoint = new Point();

            if (CenterNode.Position == new Point())
            {
                CenterNode.Position = midPoint;
                ControlNodeOne.Position = pt1;
                ControlNodeTwo.Position = pt2;
            }


            //List<Point> listPoints = new List<Point>()
            //{
            //    ControlNodeOne.Position,
            //    CenterNode.Position,
            //    ControlNodeTwo.Position,
            //    pt2
            //};

			context.BeginFigure(pt1, false, false);

            //context.BezierTo(ControlNodeTwo.Position, ControlNodeOne.Position, pt2, true, true);
            if (this.Type == VisualEdgeType.Secondary)
            {
                context.QuadraticBezierTo(CenterNode.Position, pt2, true, true);
                arrowPoint = CenterNode.Position;
            }
            else
            {
                context.BezierTo(ControlNodeOne.Position, ControlNodeTwo.Position, pt2, true, true);
                arrowPoint = ControlNodeTwo.Position;
            }

            //context.BezierTo(ControlNodeOne.Position, CenterNode.Position, pt2, true, true);
            //context.BezierTo(CenterNode.Position, ControlNodeOne.Position, pt2, true, true);
            //context.QuadraticBezierTo(CenterNode.Position, pt2, true, true);
            //context.PolyQuadraticBezierTo(listPoints, true, true);
            //context.PolyLineTo(listPoints, true, true);

			if (Arrow == ArrowType.Single || Arrow == ArrowType.Double)
			{
                double theta = Math.Atan2(arrowPoint.Y - pt2.Y, arrowPoint.X - pt2.X);
				double sint = Math.Sin(theta);
				double cost = Math.Cos(theta);

				Point pt3 = new Point(
					 pt2.X + (HeadWidth * cost - HeadHeight * sint),
					 pt2.Y + (HeadWidth * sint + HeadHeight * cost));

				Point pt4 = new Point(
					 pt2.X + (HeadWidth * cost + HeadHeight * sint),
					 pt2.Y - (HeadHeight * cost - HeadWidth * sint));

				context.BeginFigure(pt3, true, true);
				context.LineTo(pt2, true, true);
				context.LineTo(pt4, true, true);

				if (Arrow == ArrowType.Double)
				{
                    theta = Math.Atan2(pt1.Y - arrowPoint.Y, pt1.X - arrowPoint.X);
                    sint = Math.Sin(theta);
                    cost = Math.Cos(theta);
					Point pt5 = new Point(
						 pt1.X - (HeadWidth * cost - HeadHeight * sint),
						 pt1.Y - (HeadWidth * sint + HeadHeight * cost));

					Point pt6 = new Point(
						 pt1.X - (HeadWidth * cost + HeadHeight * sint),
						 pt1.Y + (HeadHeight * cost - HeadWidth * sint));

					context.BeginFigure(pt5, true, true);
					context.LineTo(pt1, true, true);
					context.LineTo(pt6, true, true);
				}
			}
		}

        public void UpdateStyles()
        {
            OnPropertyChanged("VisualEdgeStrokeBrush");

            if (Type == VisualEdgeType.Secondary)
                Arrow = ArrowType.Double;
            else
                Arrow = ArrowType.Single;
            //Stroke = StrokeBrush;
            // TODO: The code to determine line thickness / dotted / whatever would go here.
            //          this should house styling considerations dependent on what kind of edge it is
        }

        private void CreateNewLabel()
        {
            m_tbLabel = new TextBlock();
            m_tbLabel.Style = (Style)TryFindResource("LabelStyle");
            m_tbLabel.Text = m_startNode.Node.RelationTo(m_endNode.Node).ToString();
            m_tbLabel.SizeChanged += new SizeChangedEventHandler(m_tbLabel_SizeChanged);
        }
        //private void RepositionLabel()
        //{
        //    Vector v = m_startNode.Position - m_endNode.Position;
        //    double diff = v.Length / (v.Length / 3);
        //    Point point = new Point(EndPoint.X + (v.X / diff), EndPoint.Y + (v.Y / diff));
        //    Canvas.SetLeft(m_tbLabel, point.X - (m_tbLabel.ActualWidth / 2));
        //    Canvas.SetTop(m_tbLabel, point.Y - (m_tbLabel.ActualHeight / 2));
        //}
        private Point RepositionLabel(Point from, Point to)
        {
            Vector v = from - to;
            double diff = v.Length / (v.Length / 3);
            Point point = new Point(to.X + (v.X / diff), to.Y + (v.Y / diff));
            Canvas.SetLeft(m_tbLabel, point.X - (m_tbLabel.ActualWidth / 2));
            Canvas.SetTop(m_tbLabel, point.Y - (m_tbLabel.ActualHeight / 2));
            return point;
        }



        private TextBlock m_tbLabel;
        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged(string info)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(info));
        }

        #endregion
    }
}
